﻿/* Copyright (c) 2010 Lexalytics Inc.

 Permission is hereby granted, free of charge, to any person
 obtaining a copy of this software and associated documentation
 files (the "Software"), to deal in the Software without
 restriction, including without limitation the rights to use,
 copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the
 Software is furnished to do so, subject to the following
 conditions:

 The above copyright notice and this permission notice shall be
 included in all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace LexaScopeNET
{
    /// <summary>
    /// Public base class for interpreting responses from web service calls
    /// </summary>
    public class LXAResponse
    {
        // Private data members
        private int _nLastError = 0;
        private string _strLastError = "";

        /// <summary>
        /// Property access to last error received from web service call
        /// </summary>
        public int LastError { get { return _nLastError; } }
        /// <summary>
        /// Property access to error string
        /// </summary>
        public string LastErrorString { get {return _strLastError; } }

        /// <summary>
        /// Base class for functionality needed to interpret web service responses.
        /// </summary>
        /// <param name="responseContent">XML response to a web service call</param>
        /// <exception cref="System.Exception">Thrown if exception caught loading response XML</exception>
        public LXAResponse(XmlDocument responseContent)
        {
            // Set the internal XML document to the response content provided
            try
            {
                // Check if we've received a PHP warning as part of response
                if (responseContent.InnerText.Contains("<b>Warning</b>"))
                {
                    _nLastError = -1;
                    _strLastError = responseContent.InnerText;
                    return;
                }
                XmlNode aNode = responseContent.SelectSingleNode("/lxa_webapi");
                if (aNode.HasChildNodes)
                {
                    foreach (XmlNode childNode in aNode.ChildNodes)
                    {
                        switch (childNode.Name)
                        {
                            case "errorcode":
                                _nLastError = Convert.ToInt32(childNode.InnerText);
                                break;
                            case "errormessage":
                                _strLastError = childNode.InnerText;
                                break;
                        }
                    }
                }
            }
            catch (Exception err)
            {
                Exception resErr = new Exception("An generic error occurred while evaluating the response to web service call.", err);
                throw resErr;
            }
        }

    }

    /// <summary>
    /// Public class for response handling GetProjectList calls
    /// </summary>
    public class GetProjectListResponse : LXAResponse
    {
        /// <summary>
        /// Private data member containing list of project names
        /// </summary>
        private List<string> _lstProjects = new List<string>();

        /// <summary>
        /// Public read-only access to project names
        /// </summary>
        public List<string> ProjectNames { get { return _lstProjects; } }

        /// <summary>
        /// Constructor, calls base constructor first 
        /// </summary>
        /// <param name="responseContent">XmlDocument containing web service response</param>
        /// <exception cref="System.Exception">Thrown if exception caught while reading response</exception>
        public GetProjectListResponse(XmlDocument responseContent)
            : base(responseContent)
        {
            try
            {
                // Select a node list based on expected XPath
                XmlNodeList projectResults = responseContent.SelectNodes("lxa_webapi/results/row");
                foreach (XmlNode aResult in projectResults)
                {
                    string strProjectName = "";
                    foreach (XmlNode aNode in aResult)
                    {
                        switch (aNode.Name.ToLower())
                        {
                            case "label":
                                strProjectName = aNode.InnerText;
                                break;
                        }
                    }
                    _lstProjects.Add(strProjectName);
                }
            }
            catch (Exception err)
            {
                Exception resErr = new Exception("An error occurred while evaluating the response to the get existing projects.", err);
                throw resErr;
            }
        }
    }

    /// <summary>
    /// Public class for response handling of a single assets results from web service
    /// </summary>
    public class GetSingleResultResponse : LXAResponse
    {
        private string _LastDate = "";
        public string LastDate { get { return _LastDate; } }

        private int _NumResults = 1;
        public int NumResults { get { return _NumResults; } }

        /// <summary>
        /// Constructor, calls base constructor first 
        /// </summary>
        /// <param name="responseContent">XmlDocument containing web service response</param>
        /// <param name="identifier">A string to use as result identifier</param>
        /// <param name="myResults">List of Zephyr Result objects</param>
        /// <exception cref="System.Exception">Thrown if exception caught while reading response</exception>
        public GetSingleResultResponse(XmlDocument responseContent, string identifier, ref List<Zephyr.Result> myResults)
            : base(responseContent)
        {
            try
            {
                // Select a node list based on expected XPath
                Zephyr.Result aResult = new LexaScopeNET.Zephyr.Result(identifier,0.0);
                XmlNodeList assetRows = responseContent.SelectNodes("lxa_webapi");
                foreach (XmlNode aAsset in assetRows)
                {
                    // Iterate through child nodes
                    XmlNodeList resultRows = aAsset.ChildNodes;
                    foreach (XmlNode aChildResult in resultRows)
                    {
                        // Get entity, theme, and metadata results
                        switch (aChildResult.Name.ToLower())
                        {
                            case "sentiment": // sentiment results
                                InsertSentimentResults(identifier, aAsset, ref aResult);
                                break;
                            case "themes": // theme results
                                InsertThemeResults(identifier, aAsset, ref aResult);
                                break;
                        }
                    }
                }
                myResults.Add(aResult);
            }
            catch (Exception err)
            {
                Exception resErr = new Exception("An error occurred while evaluating the response to the get entities.", err);
                throw resErr;
            }
        }

        #region Private methods for handling different types of result rows
        /// <summary>
        /// Inserts entity result rows into the dataset
        /// </summary>
        /// <param name="strAsset"></param>
        /// <param name="nodeResult"></param>
        /// <param name="dsResults"></param>
        private void InsertEntityResults(string strAsset, XmlNode nodeResult, ref Zephyr.Result aResult)
        {
            XmlNodeList resultRows = nodeResult.SelectNodes("row");
            try
            {
                if (resultRows.Count > 0)
                {
                    string strText = "";
                    string strType = "";
                    string strLabel = "";
                    int nSentiment = 0;
                    double dMagnitude = 0.0;
                    int nMentions = 0;

                    foreach (XmlNode aRow in resultRows)
                    {
                        foreach (XmlNode aField in aRow.ChildNodes)
                        {
                            switch (aField.Name.ToLower())
                            {
                                case "entity":
                                    strText = aField.InnerText;
                                    break;
                                case "type":
                                    strType = aField.InnerText;
                                    break;
                                case "label":
                                    strLabel = aField.InnerText;
                                    break;
                                case "sentiment":
                                    if (aField.InnerText.Length > 0)
                                        if (!Int32.TryParse(aField.InnerText, out nSentiment))
                                            nSentiment = 0;
                                    break;
                                case "magnitude":
                                    if (aField.InnerText.Length > 0)
                                        if (!Double.TryParse(aField.InnerText, out dMagnitude))
                                            dMagnitude = 0.0;
                                    break;
                                case "mentions":
                                    if (aField.InnerText.Length > 0)
                                        if (!Int32.TryParse(aField.InnerText, out nMentions))
                                            nMentions = 0;
                                    break;
                            }
                        }
                        if ((strText.Length > 0) && (strType.Length > 0) && (strLabel.Length > 0))
                            aResult.AssetEntities.Add(new LexaScopeNET.Zephyr.Entity() { Text = strText, Type = strType, Label = strLabel, Magnitude = dMagnitude, Sentiment = nSentiment, Mentions = nMentions });
                    }
                }
            }
            catch (Exception err)
            {
                System.Diagnostics.Trace.WriteLine(err.Message);
            }
            return;
        }

        /// <summary>
        /// Inserts theme result rows into the dataset
        /// </summary>
        /// <param name="strAsset"></param>
        /// <param name="nodeResult"></param>
        /// <param name="dsResults"></param>
        private void InsertThemeResults(string strAsset, XmlNode nodeResult, ref Zephyr.Result aResult)
        {
            XmlNodeList resultRows = nodeResult.SelectNodes("themes/row");
            try
            {
                if (resultRows.Count > 0)
                {
                    string strText = "";
                    int nType = 0;
                    double dScore = 0.0;
                    double dSentiment = 0;
                    int nEvidence = 0;
                    string strSummary = "";

                    foreach (XmlNode aRow in resultRows)
                    {
                        foreach (XmlNode aField in aRow.ChildNodes)
                        {
                            switch (aField.Name.ToLower())
                            {
                                case "theme":
                                    strText = aField.InnerText;
                                    break;
                                case "score":
                                    if (aField.InnerText.Length > 0)
                                        if (!Double.TryParse(aField.InnerText, out dScore))
                                            dScore = 0.0;
                                    break;
                                case "type":
                                    if (aField.InnerText.Length > 0)
                                        if (!Int32.TryParse(aField.InnerText, out nType))
                                            nType = 0;
                                    break;
                                case "sentiment":
                                    if (aField.InnerText.Length > 0)
                                        if (!Double.TryParse(aField.InnerText, out dSentiment))
                                            dSentiment = 0.0;
                                    break;
                                case "evidence":
                                    if (aField.InnerText.Length > 0)
                                        if (!Int32.TryParse(aField.InnerText, out nEvidence))
                                            nEvidence = 0;
                                    break;
                                case "summary":
                                    strSummary = aField.InnerText;
                                    break;
                            }
                        }
                        if ((strText.Length > 0))
                            aResult.AssetThemes.Add(new LexaScopeNET.Zephyr.Theme() { Text = strText, Type = nType, Score = dScore, Magnitude = dSentiment, Evidence = nEvidence, Summary = strSummary});
                    }
                }
            }
            catch (Exception err)
            {
                System.Diagnostics.Trace.WriteLine(err.Message);
            }
            return;
        }

        /// <summary>
        /// Inserts sentiment result rows into the Results structure
        /// </summary>
        /// <param name="strAsset"></param>
        /// <param name="nodeResult"></param>
        /// <param name="dsResults"></param>
        private void InsertSentimentResults(string strAsset, XmlNode nodeResult, ref Zephyr.Result aResult)
        {
            XmlNodeList resultRows = nodeResult.SelectNodes("sentiment");
            try
            {
                if (resultRows.Count > 0)
                {
                    double dScore = 0.0;
                    Zephyr.Sentiment aSentiment = new LexaScopeNET.Zephyr.Sentiment();

                    foreach (XmlNode aRow in resultRows)
                    {
                        foreach (XmlNode aField in aRow.ChildNodes)
                        {
                            switch (aField.Name.ToLower())
                            {
                                // This is the item sentiment score based on phrases
                                case "score":   
                                    if (aField.InnerText.Length > 0)
                                    {
                                        if (!Double.TryParse(aField.InnerText, out dScore))
                                            dScore = 0.0;

                                        aResult.Sentiment = dScore;
                                        aSentiment.Score = dScore;
                                    }
                                    break;
                                    
                                // These are sentiment-bearing phrases that contributed to the sentiment score
                                case "row":
                                    XmlNodeList phraseRows = aField.SelectNodes("row");
                                    foreach (XmlNode aPhrase in phraseRows)
                                    {
                                        Zephyr.SentimentPhrase aSentimentPhrase = new LexaScopeNET.Zephyr.SentimentPhrase();
                                        foreach (XmlNode aPhraseChild in aPhrase)
                                        {
                                            switch (aPhraseChild.Name.ToLower())
                                            {
                                                // This is the sentiment-bearing phrase
                                                case "phrase":
                                                    aSentimentPhrase.Text = aPhraseChild.InnerText;
                                                    break;
                                                // This is the sentiment score of the phrase
                                                case "score":
                                                    if (aPhraseChild.InnerText.Length > 0)
                                                    {
                                                        if (!Double.TryParse(aPhraseChild.InnerText, out dScore))
                                                            dScore = 0.0;

                                                        aSentimentPhrase.Score = dScore;
                                                    }
                                                    break;
                                                // This is the type of sentiment phrase returned
                                                case "type":
                                                    if (aPhraseChild.InnerText.Length > 0)
                                                    {
                                                        int nVal = 0;
                                                        if (!Int32.TryParse(aPhraseChild.InnerText, out nVal))
                                                            nVal = 0;

                                                        aSentimentPhrase.Type = nVal;
                                                    }
                                                    break;
                                                // This indicates if the sentiment phrase has been inverted
                                                case "inverted":
                                                    if (aPhraseChild.InnerText.Length > 0)
                                                    {
                                                        int nVal = 0;
                                                        if (!Int32.TryParse(aPhraseChild.InnerText, out nVal))
                                                            nVal = 0;

                                                        aSentimentPhrase.Inverted = nVal;
                                                    }
                                                    break;
                                                // If applicable, this is the inverting phrase
                                                case "invertingphrase":
                                                    aSentimentPhrase.InvertingPhrase = aPhraseChild.InnerText;
                                                    break;
                                            }
                                        }
                                        aSentiment.Phrases.Add(aSentimentPhrase);
                                    }
                                    break;

                                // If available, these are scores from our default model
                                // They indicate how likely the document is pos/neg/neu/mixed
                                case "model_positive":
                                case "model_negative":
                                case "model_neutral":
                                case "model_mixed":
                                    if (aField.InnerText.Length > 0)
                                    {
                                        if (!Double.TryParse(aField.InnerText, out dScore))
                                            dScore = 0.0;

                                        aSentiment.ModelScores.Add(aField.Name, dScore);
                                    }
                                    break;
                            }
                        }

                        aResult.AssetSentiment.Add(aSentiment);
                    }
                }
            }
            catch (Exception err)
            {
                System.Diagnostics.Trace.WriteLine(err.Message);
            }
            return;
        }

        /// <summary>
        /// Inserts metadata result rows into the dataset
        /// </summary>
        /// <param name="strAsset"></param>
        /// <param name="nodeResult"></param>
        /// <param name="dsResults"></param>
        private void InsertMetadataResults(string strAsset, XmlNode nodeResult, ref Zephyr.Result aResult)
        {
            XmlNodeList resultFields = nodeResult.SelectNodes("row");
            foreach (XmlNode aRow in resultFields)
            {
                if (aRow.HasChildNodes)
                {
                    string strLabel = "";
                    string strValue = "";

                    foreach (XmlNode aField in aRow.ChildNodes)
                    {
                        switch (aField.Name.ToLower())
                        {
                            case "label":
                                strLabel = aField.InnerText;
                                break;
                            case "value":
                                strValue = aField.InnerText;
                                break;
                        }
                    }
                    if ((strLabel.Length > 0) && (strValue.Length > 0))
                        aResult.AssetMetadata.Add(new LexaScopeNET.Zephyr.Metadata() { Label = strLabel, Value = strValue });
                }
            }
            return;
        }

        #endregion
    }

    /// <summary>
    /// Public class for response handling of results from Tempest web service
    /// </summary>
    public class GetResultsResponse : LXAResponse
    {
        private string _LastDate = "";
        public string LastDate { get { return _LastDate; } }

        private int _NumResults = 0;
        public int NumResults { get { return _NumResults; } }

        /// <summary>
        /// Constructor, calls base constructor first 
        /// </summary>
        /// <param name="responseContent">XmlDocument containing web service response</param>
        /// <exception cref="System.Exception">Thrown if exception caught while reading response</exception>
        public GetResultsResponse(XmlDocument responseContent, ref List<Zephyr.Result> myResults) : base(responseContent)
        {
            try
            {
                // Select a node list based on expected XPath
                XmlNodeList assetRows = responseContent.SelectNodes("lxa_webapi/results/row");
                foreach (XmlNode aAsset in assetRows)
                {
                    string strIdentifier = "";
                    double dSentiment = 0.0;
                    // Get non-row (asset) results
                    foreach (XmlNode aNode in aAsset)
                    {
                        switch (aNode.Name.ToLower())
                        {
                            case "identifier":
                                strIdentifier = aNode.InnerText;
                                break;
                            case "sentiment":
                                if (aNode.InnerText.Length>0)
                                    dSentiment = Convert.ToDouble(aNode.InnerText);
                                break;
                        }
                    }
                    // Add as row to asset table in dataset
                    if (strIdentifier.Length > 0)
                    {
                        Zephyr.Result aResult = new LexaScopeNET.Zephyr.Result(strIdentifier, dSentiment);
                        // Get entity, theme, and metadata results
                        XmlNodeList resultRows = aAsset.SelectNodes("row");
                        int nChildIndex = 0;
                        foreach (XmlNode aChildResult in resultRows)
                        {
                            switch (nChildIndex)
                            {
                                case 0: // entity results
                                    InsertEntityResults(strIdentifier, aChildResult, ref aResult);
                                    break;
                                case 1: // theme results
                                    InsertThemeResults(strIdentifier, aChildResult, ref aResult);
                                    break;
                                case 2: // metadata results
                                    InsertMetadataResults(strIdentifier, aChildResult, ref aResult);
                                    break;
                            }
                            nChildIndex++;
                        }
                        myResults.Add(aResult);
                    }
                }

                // Determine if there is a next token element and number of total results expected
                XmlNode aLastDate = responseContent.SelectSingleNode("lxa_webapi/last_date");
                if (aLastDate != null)
                    _LastDate = aLastDate.InnerText;
            }
            catch (Exception err)
            {
                Exception resErr = new Exception("An error occurred while evaluating the response to the get entities.", err);
                throw resErr;
            }
        }

        #region Private methods for handling different types of result rows
        /// <summary>
        /// Inserts entity result rows into the dataset
        /// </summary>
        /// <param name="strAsset"></param>
        /// <param name="nodeResult"></param>
        /// <param name="dsResults"></param>
        private void InsertEntityResults(string strAsset, XmlNode nodeResult, ref Zephyr.Result aResult)
        {
            XmlNodeList resultRows = nodeResult.SelectNodes("row");
            try
            {
                if (resultRows.Count > 0)
                {
                    string strText = "";
                    string strType = "";
                    string strLabel = "";
                    int nSentiment = 0;
                    double dMagnitude = 0.0;
                    int nMentions = 0;

                    foreach (XmlNode aRow in resultRows)
                    {
                        foreach (XmlNode aField in aRow.ChildNodes)
                        {
                            switch (aField.Name.ToLower())
                            {
                                case "entity":
                                    strText = aField.InnerText;
                                    break;
                                case "type":
                                    strType = aField.InnerText;
                                    break;
                                case "label":
                                    strLabel = aField.InnerText;
                                    break;
                                case "sentiment":
                                    if (aField.InnerText.Length > 0)                          
                                        if (!Int32.TryParse(aField.InnerText, out nSentiment))
                                            nSentiment = 0;
                                    break;
                                case "magnitude":
                                    if (aField.InnerText.Length > 0)
                                        if (!Double.TryParse(aField.InnerText, out dMagnitude))
                                            dMagnitude = 0.0;
                                    break;
                                case "mentions":
                                    if (aField.InnerText.Length > 0)
                                        if (!Int32.TryParse(aField.InnerText, out nMentions))
                                            nMentions = 0;
                                    break;
                            }
                        }
                        if ((strText.Length > 0) && (strType.Length > 0) && (strLabel.Length > 0))
                            aResult.AssetEntities.Add(new LexaScopeNET.Zephyr.Entity() { Text = strText, Type = strType, Label = strLabel, Magnitude = dMagnitude, Sentiment = nSentiment, Mentions = nMentions });
                    }
                }
            }
            catch (Exception err)
            {
                System.Diagnostics.Trace.WriteLine(err.Message);
            }
            return;
        }

        /// <summary>
        /// Inserts theme result rows into the dataset
        /// </summary>
        /// <param name="strAsset"></param>
        /// <param name="nodeResult"></param>
        /// <param name="dsResults"></param>
        private void InsertThemeResults(string strAsset, XmlNode nodeResult, ref Zephyr.Result aResult)
        {
            XmlNodeList resultRows = nodeResult.SelectNodes("row");
            try
            {
                if (resultRows.Count > 0)
                {
                    string strText = "";
                    int nType = 0;
                    double dScore = 0.0;
                    int nSentiment = 0;
                    double dMagnitude = 0.0;

                    foreach (XmlNode aRow in resultRows)
                    {
                        foreach (XmlNode aField in aRow.ChildNodes)
                        {
                            switch (aField.Name.ToLower())
                            {
                                case "theme":
                                    strText = aField.InnerText;
                                    break;
                                case "score":
                                    if (aField.InnerText.Length > 0)
                                        if (!Double.TryParse(aField.InnerText, out dScore))
                                            dScore = 0.0;
                                    break;
                                case "type":
                                    if (aField.InnerText.Length > 0)
                                        if (!Int32.TryParse(aField.InnerText, out nType))
                                            nType = 0;
                                    break;
                                case "sentiment":
                                    if (aField.InnerText.Length > 0)
                                        if (!Int32.TryParse(aField.InnerText, out nSentiment))
                                            nSentiment = 0;
                                    break;
                                case "magnitude":
                                    if (aField.InnerText.Length > 0)
                                        if (!Double.TryParse(aField.InnerText, out dMagnitude))
                                            dMagnitude = 0.0;
                                    break;
                            }
                        }
                        if ((strText.Length > 0))
                            aResult.AssetThemes.Add(new LexaScopeNET.Zephyr.Theme() { Text = strText, Type = nType, Score = dScore, Magnitude = dMagnitude, Sentiment = nSentiment });
                    }
                }
            }
            catch (Exception err)
            {
                System.Diagnostics.Trace.WriteLine(err.Message);
            }
            return;
        }

        /// <summary>
        /// Inserts metadata result rows into the dataset
        /// </summary>
        /// <param name="strAsset"></param>
        /// <param name="nodeResult"></param>
        /// <param name="dsResults"></param>
        private void InsertMetadataResults(string strAsset, XmlNode nodeResult, ref Zephyr.Result aResult)
        {
            XmlNodeList resultFields = nodeResult.SelectNodes("row");
            foreach (XmlNode aRow in resultFields)
            {
                if (aRow.HasChildNodes)
                {
                    string strLabel = "";
                    string strValue = "";

                    foreach (XmlNode aField in aRow.ChildNodes)
                    {
                        switch (aField.Name.ToLower())
                        {
                            case "label":
                                strLabel = aField.InnerText;
                                break;
                            case "value":
                                strValue = aField.InnerText;
                                break;
                        }
                    }
                    if ((strLabel.Length > 0) && (strValue.Length>0))
                        aResult.AssetMetadata.Add(new LexaScopeNET.Zephyr.Metadata() { Label=strLabel, Value=strValue});
                }
            }
            return;
        }
        
        #endregion
    }

    /// <summary>
    /// Public class for response handling of count of results from Tempest web service
    /// </summary>
    public class GetResultCountResponse : LXAResponse
    {
        /// <summary>
        /// Constructor, calls base constructor first 
        /// </summary>
        /// <param name="responseContent">XmlDocument containing web service response</param>
        /// <exception cref="System.Exception">Thrown if exception caught while reading response</exception>
        public GetResultCountResponse(XmlDocument responseContent, ref int ResultCount)
            : base(responseContent)
        {
            try
            {
                // Select a node list based on expected XPath
                XmlNode resultCount = responseContent.SelectSingleNode("lxa_webapi/total");
                if (resultCount != null)
                    ResultCount = Convert.ToInt32(resultCount.InnerText);
            }
            catch (Exception err)
            {
                Exception resErr = new Exception("An error occurred while evaluating the response to the web service call.", err);
                throw resErr;
            }
        }
    }

}
